/* eslint-disable no-unused-vars */

import Formatter from './Formatter'
import vkbeautify from 'vkbeautify'
import js_beautify from 'js-beautify'
import dayjs from 'dayjs'
import CryptoJS from 'crypto-js'
import Store from './Store';

const KEY_EDITOR_FONT_SIZE = 'editor-font-size'
const KEY_EDITOR_READ_ONLY = 'editor-read-only'

const setUpLogTheme = (monaco) => {
    monaco.languages.register({ id: 'log' });
    monaco.languages.setMonarchTokensProvider('log', {
        tokenizer: {
            root: [
                [/\[ERROR\]/, "log-error"],
                [/^\tat .*/, "log-error"],
                [/\[INFO \]/, "log-info"],
                [/\[WARN \]/, "log-warn"],
                [/URL:.* /, "log-url"],
                // 20-04-22 10:38:05.501
                [/\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d+/, "log-date"],
                [/TAG - StudyV6View/, "log-study-v6"],
                [/JSInterfaceV6 /, "log-JSInterfaceV6"],
                [/TAG - AsecSession/, "log-AsecSession"],
                [/JS-LOG:.*/, "log-js-log"],
                [/LogMgr - upload task onSuccess/, "log-task-upload-success"],
            ]
        }
    });
    monaco.editor.defineTheme('EditorTheme', {
        base: 'vs',
        inherit: true,
        rules: [
            { token: 'log-info', foreground: '008000' },
            { token: 'log-error', foreground: 'ff0000', fontStyle: 'bold' },
            { token: 'log-notice', foreground: 'FFA500' },
            { token: 'log-warn', foreground: '888800' },
            { token: 'log-date', foreground: '993300' },
            { token: 'log-url', foreground: '0000ff' },
            { token: 'log-study-v6', foreground: '8e24aa' },
            { token: 'log-JSInterfaceV6', foreground: '304ffe' },
            { token: 'log-AsecSession', foreground: 'ad1457', fontStyle: 'bold' },
            { token: 'log-js-log', foreground: '6200ea', fontStyle: 'bold' },
            { token: 'log-task-upload-success', foreground: 'f50057', fontStyle: 'bold' },
        ]
    });
}

function setUpDragDrop(root, editor) {
}

/**
 * 添加格式化日期悬停提示
 * 
 * @param {import('monaco-editor')} monaco 
 */
function addTimeFormatProvider(monaco) {
    const timeFormatProvider = {
        provideHover(document, position) {
            let text = document.getValueInRange(new monaco.Range(position.lineNumber, position.column - 12, position.lineNumber, position.column + 12))
            let match = text.match(/(\d{11,13})|(\d{10})/)
            if (match) {
                let timeStr = match[0]
                let startColumn = position.column - 12 + text.indexOf(timeStr)
                let endColumn = startColumn + timeStr.length
                return {
                    contents: [{ value: Formatter.formatTimeInString(timeStr) }],
                    range: new monaco.Range(position.lineNumber, startColumn, position.lineNumber, endColumn)
                };
            } else { return null }

        }
    }
    monaco.languages.registerHoverProvider('javascript', timeFormatProvider);
    monaco.languages.registerHoverProvider('json', timeFormatProvider);
    monaco.languages.registerHoverProvider('log', timeFormatProvider);
    monaco.languages.registerHoverProvider('html', timeFormatProvider);
}

/**
 * @typedef {(text:String)=>String} EditCallback
 */
/**
 * @typedef {Object} EditOptions
 * @property {boolean} append
 * @property {boolean} insert
 * @property {boolean} noChange
 */

/**
* 
* @param {import('monaco-editor').editor.IStandaloneCodeEditor} editor 
* @param {EditOptions} option 
* @param {EditCallback} cb 
*/
function editText(editor, option, cb) {
    try {
        let operations = []
        let model = editor.getModel()
        let selection = editor.getSelection()
        let selections = editor.getSelections()
        if (selection.isEmpty()) {
            let lineCount = model.getLineCount()
            let maxColumn = model.getLineMaxColumn(lineCount)
            selections = [{
                startLineNumber: 0,
                startColumn: 0,
                endLineNumber: lineCount,
                endColumn: maxColumn,
            }]
            if (option.insert) {
                selections = [selection]
            }
        }
        for (let i = 0; i < selections.length; i++) {
            const selection = selections[i];
            let text = model.getValueInRange(selection)
            let result = cb(text)
            console.log('result:', result);
            let range = selection
            if (option.append) {
                range = {
                    startLineNumber: selection.endLineNumber,
                    startColumn: selection.endColumn,
                    endLineNumber: selection.endLineNumber,
                    endColumn: selection.endColumn,
                }
            }
            operations.push({
                range: range,
                text: result
            })
        }
        if (!option.noChange) {
            editor.executeEdits('action', operations)
        }
    } catch (error) {
        window.dispatchEvent(new ErrorEvent('error', error))
        console.error(error)
    }
}

class Actions {
    /**
     * @param {import('monaco-editor')} monaco 
     */
    static toggleSoftWrap(monaco) {
        return {
            id: 'y-wrap-id',
            label: 'Toggle Soft Wrap',
            keybindings: [
                monaco.KeyMod.Alt | monaco.KeyCode.KEY_Z,
            ],
            precondition: null,
            keybindingContext: null,
            contextMenuGroupId: 'navigation',
            contextMenuOrder: 1.5,
            run: function (ed) {
                let optionValue = ed.getOption(monaco.editor.EditorOptions.wordWrap.id)
                if (optionValue == 'on') {
                    ed.updateOptions({ wordWrap: "off" });
                } else {
                    ed.updateOptions({ wordWrap: "on" });
                }
                return null;
            }
        }
    }

    // /**
    //  * @param {import('monaco-editor')} monaco 
    //  */
    // static newEditor(monaco) {
    //     return {
    //         id: 'y-new-editor',
    //         label: 'New Editor',
    //         keybindings: [
    //             monaco.KeyMod.Alt | monaco.KeyCode.KEY_N,
    //         ],
    //         precondition: null,
    //         keybindingContext: null,
    //         contextMenuGroupId: 'navigation',
    //         contextMenuOrder: 1.5,
    //         run: function (ed) {
    //             let name = Formatter.dateStr(Date.now())
    //             let dataKey = IndexedDBStore.md5(name)
    //             window['app'].openViewEditor(name, dataKey)
    //             return null;
    //         }
    //     }
    // }

    /**
     * @param {import('monaco-editor')} monaco 
     */
    static zoomOut(monaco) {
        return {
            id: 'y-zoom-out',
            label: 'Zoom Out',
            keybindings: [
                monaco.KeyMod.Alt | monaco.KeyCode.US_OPEN_SQUARE_BRACKET,
            ],
            precondition: null,
            keybindingContext: null,
            contextMenuGroupId: 'navigation',
            contextMenuOrder: 1.5,
            run: function (ed) {
                let optionValue = ed.getOption(monaco.editor.EditorOptions.fontSize.id)
                optionValue--;
                ed.updateOptions({ fontSize: optionValue })
                Store.putCfg(KEY_EDITOR_FONT_SIZE, optionValue)
                return null;
            }
        }
    }
    /**
     * @param {import('monaco-editor')} monaco 
     */
    static zoomIn(monaco) {
        return {
            id: 'y-zoom-in',
            label: 'Zoom In',
            keybindings: [
                monaco.KeyMod.Alt | monaco.KeyCode.US_CLOSE_SQUARE_BRACKET,
            ],
            precondition: null,
            keybindingContext: null,
            contextMenuGroupId: 'navigation',
            contextMenuOrder: 1.5,
            run: function (ed) {
                let optionValue = ed.getOption(monaco.editor.EditorOptions.fontSize.id)
                optionValue++;
                ed.updateOptions({ fontSize: optionValue });
                Store.putCfg(KEY_EDITOR_FONT_SIZE, optionValue)
                return null;
            }
        }
    }
    /**
     * @param {import('monaco-editor')} monaco 
     */
    static toggleReadonly(monaco) {
        return {
            id: 'y-toggle-readonly',
            label: 'Toggle readOnly',
            keybindings: [
                monaco.KeyMod.Alt | monaco.KeyCode.US_BACKSLASH,
            ],
            precondition: null,
            keybindingContext: null,
            contextMenuGroupId: 'navigation',
            contextMenuOrder: 1.5,
            run: function (ed) {
                let optionValue = ed.getOption(monaco.editor.EditorOptions.readOnly.id)
                optionValue = !optionValue;
                ed.updateOptions({ readOnly: optionValue });
                Store.putCfg(KEY_EDITOR_READ_ONLY, optionValue)
                return null;
            }
        }
    }

    static javascript(monaco) {
        return {
            id: 'y-language-javascript',
            label: 'language javascript',
            run: async function (ed) {
                monaco.editor.setModelLanguage(ed.getModel(), 'javascript')
            }
        }
    }

    static language_log(monaco) {
        return {
            id: 'y-language-log',
            label: 'language log',
            run: async function (ed) {
                monaco.editor.setModelLanguage(ed.getModel(), 'log')
            }
        }
    }

    static html(monaco) {
        return {
            id: 'y-language-html',
            label: 'language html',
            run: async function (ed) {
                monaco.editor.setModelLanguage(ed.getModel(), 'html')
            }
        }
    }

    static json(monaco) {
        return {
            id: 'y-language-json',
            label: 'language json',
            run: async function (ed) {
                monaco.editor.setModelLanguage(ed.getModel(), 'json')
            }
        }
    }

    static xml(monaco) {
        return {
            id: 'y-language-xml',
            label: 'language xml',
            run: async function (ed) {
                monaco.editor.setModelLanguage(ed.getModel(), 'xml')
            }
        }
    }

    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static lineRemoveEmpty(monaco) {
        return {
            id: 'y-remove-empty',
            label: 'Line Remove Empty',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return text.split('\n').filter(o => o.trim()).join('\n')
                })
            }
        }
    }

    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static lineRemoveDuplicate(monaco) {
        return {
            id: 'y-remove-duplicate',
            label: 'Line Remove Duplicate',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return [...new Set(text.split('\n'))].join('\n')
                })
            }
        }
    }

    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static lineGroup(monaco) {
        return {
            id: 'y-group-duplicate',
            label: 'Line Group Duplicate',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    let map = {}
                    let lines = text.split('\n')
                    let arr = []
                    for (const line of lines) {
                        if (map[line]) {
                            map[line].count++
                        } else {
                            map[line] = { count: 1, line: line }
                            arr.push(map[line])
                        }
                    }
                    return arr.map(o => `${o.count.toString().padStart(5)}  ${o.line}`).join('\n')
                })
            }
        }
    }

    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static lineSortByNum(monaco) {
        function toNum(value) {
            let match = value.match(/(\d+\.?\d*e-\d+)|(\d+\.?\d*e\d+)|(\d*\.\d+)|(\d+)/g) || [];
            let nums = match.map((o) => parseFloat(o))
            return nums[0] || Infinity
        }
        return {
            id: 'y-sort-num',
            label: 'Line Sort Number',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    let lines = text.split('\n')
                    lines.sort((l, h) => toNum(l) - toNum(h))
                    return lines.join('\n')
                })
                ed.setSelection({ startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0, })
            }
        }
    }

    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static lineReverse(monaco) {
        function toNum(value) {
            let match = value.match(/(\d+\.?\d*e-\d+)|(\d+\.?\d*e\d+)|(\d*\.\d+)|(\d+)/g) || [];
            let nums = match.map((o) => parseFloat(o))
            return nums[0] || Infinity
        }
        return {
            id: 'y-line-reverse',
            label: 'Line Reverse',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    let lines = text.split('\n')
                    return lines.reverse().join('\n')
                })
            }
        }
    }

    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static lineTrim(monaco) {
        return {
            id: 'y-trim',
            label: 'Line trim',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return text.split('\n').map(o => o.trim()).join('\n')
                })
            }
        }
    }

    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static commentAlign(monaco) {
        return {
            id: 'y-comment-align',
            label: 'Comment Align',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    var maxLength = 0;
                    var lines = text.split('\n');
                    lines.forEach(function (item) {
                        item = item.replace('//', '#sp#//');
                        var items = item.split('#sp#');
                        if (items.length == 2) {
                            maxLength = Math.max(maxLength, items[0].length);
                        }
                    });
                    var newLines = [];
                    var m = /http.?:\/\//;
                    lines.forEach(function (item) {
                        if (!m.test(item)) {
                            item = item.replace('//', '#sp#//');
                        }
                        var items = item.split('#sp#//');
                        var newLine = items[0];
                        if (items.length == 2) {
                            if (items[0].trim().length == 0) {
                                newLine += '// ' + items[1].trim();
                            } else {
                                var space = maxLength - items[0].length;
                                newLine += ' '.repeat(space) + '// ' + items[1].trim();
                            }
                        }
                        newLines.push(newLine);
                    });
                    return newLines.join('\n');
                })
            }
        }
    }

    static formatJSON(monaco) {
        return {
            id: 'y-format-json',
            label: 'Format JSON',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return vkbeautify.json(text)
                })
            }
        }
    }
    static formatXML(monaco) {
        return {
            id: 'y-format-xml',
            label: 'Format XML',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return vkbeautify.xml(text)
                })
            }
        }
    }
    static formatJS(monaco) {
        return {
            id: 'y-format-js',
            label: 'Format JS',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return js_beautify(text)
                })
            }
        }
    }
    static formatSQL(monaco) {
        return {
            id: 'y-format-sql',
            label: 'Format SQL',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return vkbeautify.sql(text)
                })
            }
        }
    }
    static formatTime(monaco) {
        return {
            id: 'y-format-time',
            label: 'Format Time',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return text.replace(/(\d{13})|(\d{10})/g, function (val) {
                        var date = parseInt(val)
                        // java中的Integer.MAX_VALUE
                        if (date == 2147483647) { return val }
                        if (val.length == 10) {
                            if (val.startsWith('19')) { return val }
                            if (val.startsWith('20')) { return val }
                            date *= 1000
                        }
                        return dayjs(date).format('YYYY-MM-DD HH:mm:ss.SSS')
                    })
                })
            }
        }
    }
    /**
     * @returns {import('monaco-editor').editor.IEditorAction}
     */
    static parseTime(monaco) {
        return {
            id: 'y-parse-time',
            label: 'Parse Time',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    let textTrim = text.trim()
                    let millis = new Date(textTrim).getTime()
                    if (!isNaN(millis)) {
                        return '' + millis
                    }
                    return text
                })
            }
        }
    }
    static parseJSON(monaco) {
        return {
            id: 'y-codec-parseJSON',
            label: 'parse JSON',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return JSON.parse(text)
                })
            }
        }
    }
    static stringify(monaco) {
        return {
            id: 'y-codec-stringify',
            label: 'stringify',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return JSON.stringify(text)
                })
            }
        }
    }
    static encodeUri(monaco) {
        return {
            id: 'y-codec-encodeUri',
            label: 'encodeUri',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return encodeURIComponent(text)
                })
            }
        }
    }
    static encodeBase64(monaco) {
        return {
            id: 'y-codec-encodeBase64',
            label: 'encodeBase64',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(text))
                })
            }
        }
    }
    static encodeHex(monaco) {
        return {
            id: 'y-codec-encodeHex',
            label: 'encodeHex',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(text))
                })
            }
        }
    }
    static encodeNative(monaco) {
        return {
            id: 'y-codec-encodeNative',
            label: 'encodeNative',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return native2ascii(text)
                })
            }
        }
    }
    static encodeUnicode(monaco) {
        return {
            id: 'y-codec-encodeUnicode',
            label: 'encodeUnicode',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return toUnicode(text)
                })
            }
        }
    }
    static encodeEscape(monaco) {
        return {
            id: 'y-codec-encodeEscape',
            label: 'encodeEscape',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return escape(text)
                })
            }
        }
    }
    static decodeUri(monaco) {
        return {
            id: 'y-codec-decodeUri',
            label: 'decodeUri',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return decodeURIComponent(text)
                })
            }
        }
    }
    static decodeBase64(monaco) {
        return {
            id: 'y-codec-decodeBase64',
            label: 'decodeBase64',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.enc.Base64.parse(text).toString(CryptoJS.enc.Utf8)
                })
            }
        }
    }
    static decodeHex(monaco) {
        return {
            id: 'y-codec-decodeHex',
            label: 'decodeHex',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.enc.Hex.parse(text).toString(CryptoJS.enc.Utf8)
                })
            }
        }
    }
    static decodeNative(monaco) {
        return {
            id: 'y-codec-decodeNative',
            label: 'decodeNative',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return ascii2native(text)
                })
            }
        }
    }
    static decodeUnicode(monaco) {
        return {
            id: 'y-codec-decodeUnicode',
            label: 'decodeUnicode',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return fromUnicode(text)
                })
            }
        }
    }
    static decodeUnescape(monaco) {
        return {
            id: 'y-codec-decodeUnescape',
            label: 'decodeUnescape',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return unescape(text)
                })
            }
        }
    }
    static guid(monaco) {
        return {
            id: 'y-codec-guid',
            label: 'guid',
            run: async function (ed) {
                editText(ed, { insert: true }, (text) => {
                    let sb = text + Date.now() + Math.random() + Math.random() + Math.random()
                    return CryptoJS.MD5(sb).toString()
                })
            }
        }
    }
    static md5(monaco) {
        return {
            id: 'y-codec-md5',
            label: 'md5',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.MD5(text).toString()
                })
            }
        }
    }
    static sha1(monaco) {
        return {
            id: 'y-codec-sha1',
            label: 'sha1',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.SHA1(text).toString()
                })
            }
        }
    }
    static sha256(monaco) {
        return {
            id: 'y-codec-sha256',
            label: 'sha256',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.SHA256(text).toString()
                })
            }
        }
    }
    static sha512(monaco) {
        return {
            id: 'y-codec-sha512',
            label: 'sha512',
            run: async function (ed) {
                editText(ed, {}, (text) => {
                    return CryptoJS.SHA512(text).toString()
                })
            }
        }
    }
    static eval(monaco) {
        return {
            id: 'y-codec-eval',
            label: 'eval',
            run: async function (ed) {
                editText(ed, { append: true }, (text) => {
                    return `${window['eval'](text)}`
                })
            }
        }
    }
    static groupSortNumReverse(monaco) {
        return {
            id: 'y-group-sort_num-reverse',
            label: 'Group SortNum Reverse',
            /**
             * @param {import('monaco-editor').editor.IStandaloneCodeEditor} ed 
             */
            run: async function (ed) {
                await ed.getAction('y-group-duplicate').run()
                await ed.getAction('y-sort-num').run()
                await ed.getAction('y-line-reverse').run()
            }
        }
    }
}

export default {
    setUpLogTheme,
    setUpDragDrop,
    addTimeFormatProvider,
    Actions: Object.getOwnPropertyNames(Actions).map(o => Actions[o]).filter(o => typeof (o) == 'function')
}

/*
 * ascii2native
 */
var ascii2native = function (ascii) {
    var code, i, j, len, native1, words;
    words = ascii.split('\\u');
    native1 = words[0];
    for (i = j = 0, len = words.length; j < len; i = ++j) {
        code = words[i];
        if (!(i !== 0)) {
            continue;
        }
        native1 += String.fromCharCode(parseInt("0x" + (code.substr(0, 4))));
        if (code.length > 4) {
            native1 += code.substr(4, code.length);
        }
    }
    return native1;
};

var native2ascii = function (native_) {
    var ascii, charAscii, chars, code, i, j, len;
    chars = native_.split('');
    ascii = '';
    for (i = j = 0, len = chars.length; j < len; i = ++j) {
        code = Number(chars[i].charCodeAt(0));
        if (code > 127) {
            charAscii = code.toString(16);
            charAscii = new String('0000').substr(charAscii.length, 4) + charAscii;
            ascii += '\\u' + charAscii;
        } else {
            ascii += chars[i];
        }
    }
    return ascii;
};

var toUnicode = function (str) {
    var codes = []
    for (var i = 0; i < str.length; i++) {
        codes.push(("000" + str.charCodeAt(i).toString(16)).slice(-4))
    }
    return "\\u" + codes.join("\\u");
}

var fromUnicode = function (str) {
    return unescape(str.replace(/\\/g, "%"));
}
